﻿using System;
using System.Linq;
using System.Collections;
using System.Reflection;
using System.Data.Objects.DataClasses;
using UtfprLibrary.Helpers;
using System.Collections.Generic;
using UtfprLibrary.EntityFramework.Exceptions;

namespace UtfprLibrary.EntityFramework.Helpers {
    public static class EntityHelper {
        #region helper methods

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isPermitedProperty(PropertyInfo info) {
            if (isEntityStateProperty(info)) {
                return false;
            }

            if (isEntityKeyProperty(info)) {
                return false;
            }

            if (isIdProperty(info)) {
                return false;
            }

            if (isEntityCollectionProperty(info)) {
                return false;
            }

            if (isEntityReferenceProperty(info)) {
                return false;
            }

            return true;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isEntityStateProperty(PropertyInfo info) {
            if (info.PropertyType.Name == "EntityState") {
                return true;
            }
            return false;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isEntityReferenceProperty(PropertyInfo info) {
            if (info.PropertyType.Name == "EntityReference`1") {
                return true;
            }
            return false;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isEntityCollectionProperty(PropertyInfo info) {
            if (info.PropertyType.Name == "EntityCollection`1") {
                return true;
            }
            return false;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isEntityKeyProperty(PropertyInfo info) {
            if (info.PropertyType.Name == "EntityKey") {
                return true;
            }

            var customAttibutes = info.GetCustomAttributes(false);

            foreach (var customAttribute in customAttibutes) {
                if (customAttribute.GetType() == typeof(EdmScalarPropertyAttribute)) {
                    var entityKeyProperty = customAttribute.GetType()
                        .GetProperty("EntityKeyProperty");

                    if (entityKeyProperty != null) {
                        bool isEntityKey = (bool)entityKeyProperty
                            .GetValue(customAttribute, null);

                        if (isEntityKey) {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private static bool isIdProperty(PropertyInfo info) {
            if (info.Name.ToLower().EndsWith("id")) {
                return true;
            }
            return false;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="entityCollectionInfo"></param>
        /// <param name="entityCollectionNames"></param>
        /// <returns></returns>
        private static bool isEntityCollectionToInvestigate(PropertyInfo entityCollectionInfo, string[] entityCollectionNames) {
            if (entityCollectionNames.Contains(entityCollectionInfo.Name)) {
                return true;
            }
            return false;
        }

        #endregion

        /// <summary>
        /// Copia apenas as propriedades permitidas.
        /// </summary>
        /// <param name="baseEntityType">O tipo da entidade mãe.</param>
        /// <param name="newEntity">A entidade que receberá o(s) valor(es).</param>
        /// <param name="entityToCopy">A entidade com os valores a serem copiados.</param>
        private static void copyPermitedProperties(Type baseEntityType, EntityObject newEntity, EntityObject entityToCopy) {
            try {
                foreach (var property in entityToCopy.GetType().GetProperties()) {
                    if (isPermitedProperty(property)) {
                        if (property.PropertyType != baseEntityType) {
                            property.SetValue(newEntity, property.GetValue(entityToCopy, null), null);
                        }
                    }
                }
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entityToCopy"></param>
        /// <param name="newEntity"></param>
        /// <param name="entityCollectionNames"></param>
        private static void duplicate<T>(this T entityToCopy, T newEntity, string[] entityCollectionNames) where T : EntityObject {
            try {
                //para cada coleção da entidade base
                var entityCollectionProperties = EntityCollectionHelper.GetEntityCollectionProperties(entityToCopy);
                if (((ICollection)entityCollectionProperties).Count > 0) {
                    foreach (var entityCollectionProperty in entityCollectionProperties) {

                        //se for uma coleção para investigar
                        if (isEntityCollectionToInvestigate(entityCollectionProperty, entityCollectionNames)) {
                            //para cada objeto da coleção da entidade base
                            foreach (EntityObject entityCollectionEntity in (IEnumerable)LinqHelper.InvokeToList(entityCollectionProperty.GetValue(entityToCopy, null))) {

                                //cria uma entidade
                                var _entityCollectionNewEntity = (EntityObject)Activator.CreateInstance(entityCollectionEntity.GetType());

                                //pega a coleção da entidade base
                                var newEntityCollection = entityCollectionProperty.GetValue(newEntity, null);

                                //adiciona na coleção da entidade base
                                LinqHelper.InvokeAdd(newEntityCollection, _entityCollectionNewEntity);

                                //investiga
                                entityCollectionEntity.duplicate(_entityCollectionNewEntity, entityCollectionNames);

                                //copia apenas os valores permitidos
                                copyPermitedProperties(newEntity.GetType(), _entityCollectionNewEntity, entityCollectionEntity);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entityToCopy"></param>
        /// <param name="newEntity"></param>
        /// <param name="entityCollectionNames"></param>
        public static void DuplicateEntity<T>(this T entityToCopy, T newEntity, string[] entityCollectionNames) where T : EntityObject {
            try {
                entityToCopy.duplicate(newEntity, entityCollectionNames);

                //copia apenas os valores permitidos
                copyPermitedProperties(newEntity.GetType(), newEntity, entityToCopy);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        public static List<PropertyInfo> GetEntityReferenceProperties(object entity) {
            try {
                var entityReferenceProperties = entity
                    .GetType()
                    .GetProperties()
                    .Where(p => p.PropertyType.Name == "EntityReference`1")
                    .ToList();

                return entityReferenceProperties;
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        public static object GetEntityReference(object entity, PropertyInfo entityReferenceProperty) {
            try {
                return entityReferenceProperty.GetValue(entity, null);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        public static List<EntityObject> GetEntityReferences(object entity) {
            try {
                var entityReferences = new List<EntityObject>();

                foreach (var entityReferenceProperty in EntityHelper.GetEntityReferenceProperties(entity)) {
                    object entityReference = GetEntityReference(entity, entityReferenceProperty);
                    object entityValue = entityReference.GetType().GetProperty("Value").GetValue(entityReference, null);
                    if (entityValue != null) {
                        entityReferences.Add((EntityObject)entityValue);
                    }
                }

                return entityReferences;
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }
    }
}